package edu.tec.mty.core.modules.models;

//import java.util.ArrayList;

import edu.tec.mty.extras.Vector2D;
import edu.tec.mty.core.modules.Self;

public class Player {

	private Player boundTo = null;
	private double x = 0.0, y = 0.0;
	private double vx = 0.0, vy = 0.0;
	private double bodyAngle = 0.0, neckAngle = 0.0;
	private long step = 0;
	private boolean viewed = false;
	private boolean pointing = false;
	private double pointto = 0.0;
	private boolean kicking = false;
	private boolean tackling = false;
	private String team = "undefined";
	private int teamID = 2;
	private int uniformNumber = -1;
	private int ID = 0;
	private int countToDeath = 0;
	private boolean onEntryRegion = false;
	private double pDistance;
	//Debug
	private double agentXPosition = -1.0;
	private double agentYPosition = -1.0;
	private double agentBodyDirection = -1.0;
	private double agentHeadDirection = -1.0;
	private double direction = -1.0;
	private double distance = -1.0;
	private double distChange = -1.0;
	private double dirChange = -1.0;
	private double agentAmountOfSpeed = -1.0;
	private double agentDirectionOfSpeed = -1.0;
	private String message = "something strange happened";
	
	//Constructor for null initialization
	public Player() {
		
	}
	
	public Player(long cycle) {
		this.step = cycle;
		this.message = "member was not created correctly";
	}
	
	//Member from member (clone)
	public Player(Player member) {
		this.ID = member.getID();
		this.viewed = member.isViewed();
		this.x = member.getCurrentPosition().getX();
		this.y = member.getCurrentPosition().getY();
		this.vx = member.getVelocity().getXComponent();
		this.vy = member.getVelocity().getYComponent();
		this.pointing = member.isPointing();
		this.pointto = member.getPointingDirection();
		this.kicking = member.isKicking();
		this.tackling = member.isTackling();
		this.team = member.getTeam();
		this.teamID = member.getTeamID();
		this.uniformNumber = member.getUniformNumber();
		this.boundTo = member.getBound();
		this.countToDeath = member.getCountToDeath();
	}
	
	//Constructor for initialization in a position
	public Player(Position position) {
		this.x = position.getX();
		this.y = position.getY();
		this.bodyAngle = position.getBodyAngle();
		this.neckAngle = position.getNeckAngle();
	}	
	
	//Constructor for trainer and coach
	public Player(Position memberPosition, double neckAngle, Player lastModel, int step) {
		this.boundTo = lastModel;
		this.x = memberPosition.getX();
		this.y = memberPosition.getY();
		this.bodyAngle = memberPosition.getBodyAngle();
		this.neckAngle = neckAngle;
		this.step = step;
		this.viewed = true;
	}
	
	//Constructor for empty member
	public Player(long step, Player lastModel) {
		this.boundTo = lastModel;
		this.step = step;
		this.message = "empty member";
	}
	
	//Constructor for players
	public Player(Position agentPosition,
				  Vector2D speed,
				  long step,
				  double direction,
				  double distance,
				  double distChange,
				  double dirChange,
				  double bodyAngle,
				  double neckAngle) {
		this.bodyAngle = bodyAngle;
		this.neckAngle = neckAngle;
		double sourceAngle = agentPosition.getBodyAngle() + agentPosition.getNeckAngle() + direction;
		if (sourceAngle > 180.0) {
			sourceAngle -= 360.0;
		}
		if (sourceAngle < -180.0) {
			sourceAngle += 360.0;
		}
		double erx = Math.cos(Math.toRadians(sourceAngle));
		double ery = Math.sin(Math.toRadians(sourceAngle));
		this.pDistance = transformDistance(distance);
		double prx = pDistance * erx;
		double pry = pDistance * ery;	
		this.x = agentPosition.getX() + prx;
		this.y = agentPosition.getY() + pry;
		double erxm = (180.0 * erx) / (Math.PI * this.pDistance);
		double erym = (180.0 * ery) / (Math.PI * this.pDistance);
		double vry = (distChange * erym + dirChange * erx) / (ery * erym + erx * erxm);
		double vrx = (distChange - ery * vry) / erx;
//		double vxo = Self.AMOUNT_OF_SPEED * Math.cos(Math.toRadians(Self.DIRECTION_OF_SPEED));
//		double vyo = Self.AMOUNT_OF_SPEED * Math.sin(Math.toRadians(Self.DIRECTION_OF_SPEED));	
		this.vx = speed.getXComponent() + vrx;
		this.vy = speed.getYComponent() + vry;
		if (Math.sqrt(Math.pow(this.vx, 2.0) + Math.pow(this.vy, 2.0)) < 0.1) {
			this.vx = 0.0;
			this.vy = 0.0;
		}
		this.step = step;
		this.viewed = true;
		//Debug
		this.distance = this.pDistance;
		this.direction = direction;
		this.distChange = distChange;
		this.dirChange = dirChange;
		this.agentXPosition = agentPosition.getX();
		this.agentYPosition = agentPosition.getY();
		this.agentBodyDirection = agentPosition.getBodyAngle();
		this.agentHeadDirection = agentPosition.getNeckAngle();
		this.agentAmountOfSpeed = Self.AMOUNT_OF_SPEED;
		this.agentDirectionOfSpeed = Self.DIRECTION_OF_SPEED;
	}
	
	/**
	 * @param distance
	 * @return distance to minimize the error.  The coefficientes of the linear regresion were calculed using the table:
	 * from		to		mean		d'		error to mean
	 * ---------------------------------------------------
	 * 10.1		11.2	10.65		11.1	0.45
	 * 11.2		12.3	11.75		12.2	0.45
	 * 12.3		13.6	12.95		13.5	0.55
	 * 13.6		15.0	14.30		14.9	0.60
	 * 15.0		16.6	15.75		16.5	0.75
	 * 16.6		18.3	17.45		18.2	0.75
	 * 18.3		20.2	19.25		20.1	0.85
	 * 20.2		22.3	21.25		22.2	0.95
	 * 22.3		24.7	23.50		24.6	1.10
	 * 24.7		27.3	26.00		27.2	1.20
	 * 27.3		30.1	28.70		30.0	1.30
	 * 30.1		33.3	31.70		33.2	1.50
	 * 33.3		36.7	35.00		36.6	1.60
	 * 36.7		40.6	38.65		40.5	1.85
	 * 40.6		44.9	42.75		44.8	2.05
	 * 44.9		49.6	47.25		49.5	2.25
	 * 49.6		54.7	51.25		54.6	2.45
	 * 54.7		60.5	57.60		60.4	2.80
	 * 60.5		66.8	63.65		66.7	3.05
	 * 66.8		73.8	70.30		73.7	3.40
	 * 73.8		81.6	77.70		81.5	3.80
	 * 81.6		90.2	85.90		90.1	4.20
	 * 90.2		99.6	94.90		99.5	4.60	
	 * 
	 * So, error = -0.0962 + 0.0475 * d'
	 * 	
	 */
	private double transformDistance(double distance) {
		return distance + 0.0962 - 0.0475 * distance;
	}
	
	public void setUniformNumber(int unum) {
		this.uniformNumber = unum;
	}
	
	public int getUniformNumber() {
		return this.uniformNumber;
	}
	
	public boolean onEntryRegion() {
		return this.onEntryRegion;
	}
	
	public int getCountToDeath() {
		return this.countToDeath;
	}
	
	public Player getBound() {
		return this.boundTo;
	}
	
	public void bindTo(Player member) {
		if (!member.isViewed()) {
			this.countToDeath = member.getCountToDeath() + 1;
		}
		switch (member.getTeamID()) {
		case 0:
			if (this.teamID == 2) {
				this.teamID = 0;
				this.team = "mate";
				this.uniformNumber = member.getUniformNumber();
			}
			break;
		case 1:
			if (this.teamID == 2) {
				this.teamID = 1;
				this.team = "opponent";
				this.uniformNumber = member.getUniformNumber();
			}
			break;
		case 2:
			break;
		}
		this.boundTo = member;
	}
	
	public void unbind() {
		this.boundTo = null;
	}
	
	public void setID(int ID) {
		this.ID = ID;
	}
	
	public int getID() {
		return this.ID;
	}
	
	public void setPointTo(double direction) {
		this.pointing = true;
		this.pointto = direction;
	}
	
	public boolean isPointing() {
		return this.pointing;
	}
	
	public double getPointingDirection() {
		return this.pointto;
	}
	
	public void setKicking() {
		this.kicking = true;
	}
	
	public boolean isKicking() {
		return this.kicking;
	}
	
	public void setTackling() {
		this.tackling = true;
	}
	
	public boolean isTackling() {
		return this.tackling;
	}
	
	public void setTeam(String team) {
		if (team.equals("mate")) {
			this.teamID = 0;
		} else {
			this.teamID = 1;
		}
		this.team = team;
	}
	
	public void clearVelocity() {
		this.vx = 0.0;
		this.vy = 0.0;
	}
	
	public void setNotViewed() {
		this.viewed = false;
	}
	
	public boolean isViewed() {
		return this.viewed;
	}
	
	public int getTeamID() {
		return this.teamID;
	}
	
	public String getTeam() {
		return this.team;
	}
	
	//TODO: this method performs a hypothesis test to infer if the position is a possible new position for this member
	/**
	 * @param position
	 * @return a TRUE or FALSE indicating the position is in the movement threshold of this member.
	 * The threshold is computed using the distance computed in transformDistance method and the maximum speed per cycle for
	 * the member.  For the moment this max speed is a constant of 0.1155 m / cycle.
	 */
	public boolean isInThreshold(Position position) {
		double distance = this.getCurrentPosition().getDistanceTo(position);
		double error = -0.0962 + 0.0475 * distance;
		if (distance < error + 0.1155) {
			return true;
		} else {
			return false;
		}
	}
	
	public Position getCurrentPosition() {
		return new Position(this.x, this.y, this.bodyAngle, this.neckAngle);
	}
	
	public Position getPredictedPosition(int cycles) {
		return new Position(this.x + (double)cycles * this.vx, this.y + (double)cycles * this.vy);
	}
	
	public Vector2D getVelocity() {
		return new Vector2D(this.vx, this.vy);
	}
	
	public long getGlobalTime() {
		return this.step;
	}
	
	public Player clone() {
		return new Player(this);
	}
	
//	public Member getLasModelNotNull() throws Exception {
//		Member member;
//		if (this.viewed || this.boundTo == null) {
//			member = (Member)super.clone();
//		} else {
//			member = this.boundTo.getLasModelNotNull();
//		}
//		return member;
//	}
//	
//	public ArrayList<Member> getHistory(int counter, ArrayList<Member> list) {
//		if (counter == 0 || this.boundTo == null) {
//			return list;
//		} else {
//			counter--;
//			try {
//				list.add((Member)super.clone());
//			} catch (CloneNotSupportedException e) {
//				
//			}
//			return this.boundTo.getHistory(counter, list);
//		}
//	}
	
	public String printDebug() {
		String description;
		if (this.boundTo != null) {
			description = "Info:> time = " + this.step + " uniformNumber = " + this.uniformNumber + " first? " + (this.boundTo.isViewed() ? "no" : "yes") +
							 " | targetX = " + this.x +
							 " targetY = " + this.y +
							 " targetSpeedX = " + this.vx +
							 " targetSpeedY = " + this.vy +
							 " | sensingX = " + this.agentXPosition +
							 " sensingY = " + this.agentYPosition +
							 " sensingBody = " + this.agentBodyDirection +
							 " sensingHead = " + this.agentHeadDirection +
							 " sensingAmountOfSpeed = " + this.agentAmountOfSpeed +
							 " sensingDirectionOfSpeed = " + this.agentDirectionOfSpeed + 
							 " | direction = " + this.direction +
							 " distance = " + this.distance +
							 " distChange = " + this.distChange +
							 " dirChange = " + this.dirChange
							 ;
		} else {
			description = this.message;
		}
		return description;
	}
	
	//Test
	
	/*public static void main(String[] args) {
		Member member0 = new Member();
		Member member1 = new Member(new Position(0.0, 0.0, 0.0), 0.0, member0, 1);
		Member member2 = new Member(new Position(0.0, 0.0, 0.0), 0.0, null, 2);
		Member member3 = new Member(new Position(0.0, 0.0, 0.0), 0.0, member2, 3);
		Member member4 = new Member(new Position(0.0, 0.0, 0.0), 0.0, member3, 4);
		ArrayList<Member> members = member4.getHistory(5, new ArrayList<Member>());
		System.out.println("Size :" + members.size());
		for (int i = 0; i < members.size(); i++) {
			System.out.println(members.get(i).getGlobalTime());
		}
	}/**/
	
}
